package com.metel.mservice.common.listener;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.Properties;
import java.util.StringTokenizer;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpServlet;

import org.apache.log4j.PropertyConfigurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.metel.mservice.common.bean.PropertyBean;
import com.metel.mservice.common.utils.PhoneNumberUtil;
import com.metel.mservice.common.utils.SystemInfo;
import com.sec.infra.zookeeper.configuration.IZKExtensibleCommonConfigurable;

/**
 * SSM Contact Server Application Listener
 * @author hyoseong.lee
 *
 */
public class ApplicationListener extends HttpServlet implements ServletContextListener, IZKExtensibleCommonConfigurable {
	
	private static final Logger logger = LoggerFactory.getLogger(ApplicationListener.class);
	private static final long serialVersionUID = 2147218285710926414L;

	// Member for Zookeeper client
	//private ContainerServiceSupport zkContainerService;
	//private String ZKNODE_ROOT_PATH = "/SSM/OpenAPIServer";

	//private final String DEFAULT_VERSIONJS_FILENAME = "/home/chaton/contact/conf/versioncheck.js";  

	private final String DEFAULT_KEY_EXPIRE_DATE = "365";
	
	//private final long zkSessionRecoveryDelayTime = 30000L;
	//private static DateFormat ZK_NOW = new SimpleDateFormat("yyyy-MM-dd HH:mm");
	//private String instanceId;
	//private String logPath;
	//private String statusPath;

/*	static {
		ZK_NOW.setTimeZone(TimeZone.getTimeZone("GMT"));
	}*/
	/**
	 * Initiate SSM Contact Server
	 * 1. filer server public dns sync
	 * 2. version script sync
	 */
	public void contextInitialized(ServletContextEvent event) {
		// get public address in ec2
		String osName = System.getProperty("os.name");
		if (!osName.toLowerCase().contains("windows")) {
			//SystemInfo.publicAddress = Ec2Util.getPublicIpFromAmazonEC2MetatData();
			logger.info("[OpenAPIServer] Public Address : {}", SystemInfo.publicAddress);
		}

		// set private address
		try {
			SystemInfo.privateAddress = InetAddress.getLocalHost().getHostAddress();
		} catch (UnknownHostException e) {
			logger.warn("[OpenAPIServer] exception occurred when get private ip address", e);
		}

/*		if (PropertyBean.zkServiceUse) {
			ZKNODE_ROOT_PATH = PropertyBean.zkServiceNode;
			// set ZooKeeper configuration
			instanceId = Ec2Util.getInstanceIdFromAmazonEC2MetatData();
			
			logger.info("[OpenAPIServer] start ZK Service - {}", PropertyBean.zkServiceHosts);
			logPath = ZKNODE_ROOT_PATH + "/Logs";
			statusPath = ZKNODE_ROOT_PATH + "/Nodes/Status";
			zkContainerService = new ContainerServiceSupport(PropertyBean.zkServiceHosts,
					PropertyBean.zkServiceSessionTimeout,
					instanceId);
			try {
				connectToZKServer();
				startZKService();
				StringBuilder sb = new StringBuilder();
				sb.append(ZK_NOW.format(new Date())).append(", instance[").append(instanceId).append("] started successfully...");
				zkContainerService.bootSuccessfully(sb.toString());
			} catch (Exception e) {
				logger.error("[OpenAPIServer] instance(" + instanceId + ") failed to start." , e);
				zkContainerService.bootFailed(e);
			}
		}*/
		logger.info("[OpenAPIServer] serivce loaded successfully");
	}
	
/*	private void connectToZKServer() throws Exception {
		List<String> userIdPasswd = new ArrayList<String>();
		userIdPasswd.add(ZKCredentialRepository.ZK_SSM_USER_IDPASSWD);
		zkContainerService.addAuthInfo(userIdPasswd);
		zkContainerService.setACL(ZKCredentialRepository.getInstance().getSSMUserACLs(), 
				ZKCredentialRepository.getInstance().getSSMAdminACLs());

		zkContainerService.connectToZKServer();
		zkContainerService.addZKSessionShutdownHook(new IZKSessionShutdownHook() {
			@Override
			public void run() {
				ZKSessionRecoveryTimer zkSessionRecoveryTimer = new ZKSessionRecoveryTimer(zkSessionRecoveryDelayTime);
				zkSessionRecoveryTimer.startTimer();
			}
		}, 10);
	}
	
	private void startZKService() throws Exception {
		zkContainerService.startLogsService(logPath);
		zkContainerService.startStatusService(statusPath);
		zkContainerService.startAlertService();
		startZKConfigurationService();
	}
	
	private void startZKConfigurationService() throws Exception {
		// Load configuration from server on start up
		ZKConfiguration zkConfig = getConfigurationFromZKServer();
		if (zkConfig == null) {
			logger.error("[OpenAPIServer] instance({}) - zookeeper config is null", instanceId);
			return;
		}
		String serverConf = zkConfig.getServerConfig();
		if (serverConf == null)	{
			logger.error("[OpenAPIServer] instance({}) - server config is null", instanceId);
			return;
		}
		reloadServerConfig(serverConf.getBytes());

		String loggerConfig = zkConfig.getLoggerConfig();
		if (loggerConfig != null) {
			reloadLoggerConfig(loggerConfig.getBytes());
		}
		
		byte[] versionCheckJs = zkConfig.getCommonConfig().getReserved1Data();
		if (!(versionCheckJs == null || versionCheckJs.length == 0)) {
			reloadReserved1Config(versionCheckJs);	
		}

		byte[] phoneNumExpRules = zkConfig.getCommonConfig().getReserved2Data();
		if (phoneNumExpRules != null && phoneNumExpRules.length > 0) {
			reloadReserved2Config(phoneNumExpRules);	
		}

		byte[] fileSignatures = zkConfig.getCommonConfig().getReserved3Data();
		if (fileSignatures != null && fileSignatures.length > 0) {
			reloadReserved3Config(fileSignatures);	
		}
		
		byte[] versionUpgradeRule = zkConfig.getCommonConfig().getReserved4Data();
		if (versionUpgradeRule != null && versionUpgradeRule.length > 0) {
			reloadReserved4Config(versionUpgradeRule);
		}
	}

	private ZKConfiguration getConfigurationFromZKServer() throws Exception {
		if (zkContainerService == null || !zkContainerService.isConnected()) {
			throw new IllegalStateException("ZKConnection is not set");
		}
		String configPath = ZKNODE_ROOT_PATH + "/Configuration";
		String localConfigFilePath =PropertyBean.localConfFileName;
		zkContainerService.startConfigurationService(configPath, this, localConfigFilePath,true);
		return zkContainerService.readConfigData();
	}*/

	public void contextDestroyed(ServletContextEvent event) {
	}

	@Override
	public void reloadLoggerConfig(byte[] configData) {
		if(configData == null) return;

		InputStream is = null;
		try {
			Properties properties = new Properties();
			is = new ByteArrayInputStream(configData);
			logger.info("[OpenAPIServer] reloaded logger conf : {}", new String(configData));
			properties.load(is);
			PropertyConfigurator.configure(properties);
		} catch (IOException e) {
			logger.error("[OpenAPIServer] cannot apply reloaded log config", e);
		} finally {
			try {
				is.close();
			} catch (IOException e) {}
		}
		/*		
		// only support jdk logger
		LogManager logManager = LogManager.getLogManager();

		InputStream is = null;		
		try {
			is = new ByteArrayInputStream(configData);
			logManager.readConfiguration(is);

			//logger.info("[CONTAINER] reloaded logger conf : {}", new String(configData));
			logger.info("[CONTAINER] reloaded logger conf : {}" + new String(configData));
		} catch (SecurityException e) {
			logger.warn("[CONTAINER] cannot apply reloaded log config", e);
		} catch (IOException e) {
			logger.warn("[CONTAINER] cannot apply reloaded log config", e);
		} finally {
			if(is != null) try { is.close(); } catch(Exception e){}
		}
		 */
	}

	@Override
	public void reloadServerConfig(byte[] serverConf) {
		String pre_version = PropertyBean.versionJsFileName;
		Properties prop = new Properties();

		ByteArrayInputStream bais = new ByteArrayInputStream(serverConf);
		try {
			prop.load(bais);
		} catch (IOException ie) {
			logger.error("[OpenAPIServer] failed to load server config property", ie);
			return;
		} finally {
			try {
				bais.close();
			} catch (IOException e) {}
		}
		
		PropertyBean.versionJsFileName = prop.getProperty("versionjs.filename", pre_version);
		PropertyBean.keyExpireDate = 
			Integer.parseInt(prop.getProperty("key.expiredate", DEFAULT_KEY_EXPIRE_DATE));

		String allowedIPs = prop.getProperty("admin.allowedips", "");

		PropertyBean.allowedIPs = new HashSet<String>();
		try {
			StringTokenizer tokenizer = new StringTokenizer(allowedIPs, "|");
			while (tokenizer.hasMoreElements()) {
				String token = tokenizer.nextToken();
				PropertyBean.allowedIPs.add(token.trim());
			}
		} catch (Exception e) {

		}
		

		logger.info("[OpenAPIServer] reloaded server conf : {}", new String(serverConf));
	}

	/**
	 * Reload versioncheck.js
	 */
	@Override
	public void reloadReserved1Config(byte[] versionCheckJs) {
		try {
//			JavaScriptVersionChecker.getInstance().reload(versionCheckJs);
			logger.info("[OpenAPIServer] reloaded versioncheckjs : {}", new String(versionCheckJs));
		} catch (Exception e) {
			logger.error("[OpenAPIServer] exception occurred when reload version check script", e);
		}
	}

	/**
	 * Reload phone number expansion rule
	 */
	@Override
	public void reloadReserved2Config(byte[] rules) {
		try {
			PhoneNumberUtil.getInstance().setRules(rules);
			logger.info("[OpenAPIServer] reloaded phone number expansion rule : {}", new String(rules));
		} catch (Exception e) {
			logger.error("[OpenAPIServer] exception occurred when reload phone number expansion rule", e);
		}
	}
	

	/**
	 * Reload version upgrade rule
	 */
	@Override
	public void reloadReserved4Config(byte[] versionUpgradeRule) {
	}

	@Override
	public void reloadReserved5Config(byte[] arg0) {
		// ignore
	}
	
/*	class ZKSessionRecoveryTimer {

		private Timer timer;
		private TimerTask task;
		private long delayTime;

		public ZKSessionRecoveryTimer(long delayTime) {
			this.timer = new Timer();
			this.delayTime = delayTime;
		}

		public void startTimer() {
			task = new ZKSessionRecoveryTimerTask(this);
			timer.schedule(task, delayTime);
		}

		public void stopTimer() {
			logger.info("[OpenAPIServer] stop ZK session recovery timer task");
			timer.cancel();
		}

		public void restartTimer() {
			if (task != null) {
				task.cancel();
			}
			startTimer();
		}
	}*/
	
/*	class ZKSessionRecoveryTimerTask extends TimerTask {

		ZKSessionRecoveryTimer zkSessionRecoveryTimer;

		public ZKSessionRecoveryTimerTask(ZKSessionRecoveryTimer zkSessionRecoveryTimer) {
			this.zkSessionRecoveryTimer = zkSessionRecoveryTimer;    		
		}
		
		@Override
		public void run() {
			logger.info("[OpenAPIServer] start ZK session recovery timer task");

			try {
				zkContainerService.disconnectToZKServer();
				zkContainerService = null;
				zkContainerService = new ContainerServiceSupport(PropertyBean.zkServiceHosts,
						PropertyBean.zkServiceSessionTimeout,
						instanceId);
				connectToZKServer();
				startZKService();
				StringBuilder sb = new StringBuilder();
				sb.append(ZK_NOW.format(new Date())).append(", instance[").append(instanceId).append("] started successfully...");
				zkContainerService.bootSuccessfully(sb.toString());		
				zkSessionRecoveryTimer.stopTimer();
			} catch(Throwable t) {
				logger.warn("[OpenAPIServer] cannot start ZK service, so restart ZK session recovery timer task", t);
				zkSessionRecoveryTimer.restartTimer();
			}
		}
	}*/

	@Override
	public void reloadReserved3Config(byte[] arg0) {		
	}

}
